#pragma once
#include <iostream>
#include <pthread.h>
#include <queue>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>

namespace BQueue
{
    template <class T>
    class Bloack_Queue
    {
    private:
        std::queue<T> _bq;    // 阻塞队列
        int _cap;             // 队列的元素上限
        pthread_mutex_t _mtx; // 临界资源的锁
        // 1.当生产者生产满了，就不应该再生产了，应该让消费者来消费
        // 2.当消费者消费空了，就不应该再消费了，应该让生产者来生产
        pthread_cond_t _full;  // _bq满的，消费者在该条件变量下等待
        pthread_cond_t _empty; // _bq空的，生产者在该条件变量下等待

    private:
        void LockBlockQueue()
        {
            pthread_mutex_lock(&_mtx);
        }

        void UnlockBlockQueue()
        {
            pthread_mutex_unlock(&_mtx);
        }

        bool IsFull()
        {
            return _bq.size() == _cap;
        }

        bool IsEmpty()
        {
            return _bq.size() == 0;
        }

        void ConsumerWait()
        {
            pthread_cond_wait(&_full, &_mtx);
        }

        void ProductorWait()
        {
            // pthread_cond_wait
            // 1. 调用这个函数首先会释放锁，然后线程挂起
            // 2. 返回的时候，线程会首先自动竞争锁，获取到锁
            pthread_cond_wait(&_empty, &_mtx);
        }

        void WakeupConsumer()
        {
            pthread_cond_signal(&_full);
        }

        void WakeupProductor()
        {
            pthread_cond_signal(&_empty);
        }

    public:
        Bloack_Queue()
            : _cap(5)
        {
            pthread_mutex_init(&_mtx, nullptr);
            pthread_cond_init(&_full, nullptr);
            pthread_cond_init(&_empty, nullptr);
        }
        // 输入型参数：const &
        // 输出型参数：*
        // 输入输出型参数：&
        void Push(const T &in)
        {
            // 保证不同线程之间互斥
            LockBlockQueue();
            // 向阻塞队列中放数据
            // 如何队列已经满了，则不应该继续放，并且让消费者拿

            // if(IsFull())这样是存在Bug的，因为pthread_cond_wait可能存在挂起失败/伪唤醒的情况
            // 所以使用if判断，执行ProductorWait()失败后，会继续push数据，这样是不合理的。
            // 因此修改为使用while循环的方式检测，ProductorWait()挂起返回时，再次检测条件是否满足，如果队列还是满的，继续挂起等待
            // 保证跳出循环时，阻塞队列已经不满足IsFull()
            while (IsFull())
            {
                // 生产者等待
                ProductorWait();
                // 1.如果挂起失败了呢？
                // 2.如果被伪唤醒了呢？
            }
            _bq.push(in);

            // 唤醒消费者
            // if (_bq.size() > _cap / 2)
            //     WakeupConsumer();

            UnlockBlockQueue();
            WakeupConsumer();
        }

        void Pop(T *out)
        {
            // 保证不同线程之间互斥
            LockBlockQueue();
            // 从阻塞队列中拿数据
            // 如果队列中为空，则不应该继续拿，并且让生产者放
            // 使用循环的方式检测，保证跳出循环时，阻塞队列已经不满足IsEmpty()
            while (IsEmpty())
            {
                // 消费者等待
                ConsumerWait();
            }
            *out = _bq.front();
            _bq.pop();

            // 唤醒生产者
            // if (_bq.size() < _cap / 2)
            //     WakeupProductor();

            UnlockBlockQueue();
            WakeupProductor();
        }

        ~Bloack_Queue()
        {
            pthread_mutex_destroy(&_mtx);
            pthread_cond_destroy(&_full);
            pthread_cond_destroy(&_empty);
        }
    };
}
